/*
 * Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package javax.swing;

import java.awt.*;
import java.awt.event.*;
import java.awt.im.InputContext;
import java.io.*;
import java.text.*;
import java.util.*;
import javax.swing.event.*;
import javax.swing.plaf.UIResource;
import javax.swing.text.*;

/**
 * <code>JFormattedTextField</code> extends <code>JTextField</code> adding support for formatting
 * arbitrary values, as well as retrieving a particular object once the user has edited the text.
 * The following illustrates configuring a <code>JFormattedTextField</code> to edit dates:
 * <pre>
 *   JFormattedTextField ftf = new JFormattedTextField();
 *   ftf.setValue(new Date());
 * </pre>
 * <p> Once a <code>JFormattedTextField</code> has been created, you can listen for editing changes
 * by way of adding a <code>PropertyChangeListener</code> and listening for
 * <code>PropertyChangeEvent</code>s with the property name <code>value</code>. <p>
 * <code>JFormattedTextField</code> allows configuring what action should be taken when focus is
 * lost. The possible configurations are: <table summary="Possible JFormattedTextField
 * configurations and their descriptions"> <tr><th><p style="text-align:left">Value</p></th><th><p
 * style="text-align:left">Description</p></th></tr> <tr><td>JFormattedTextField.REVERT <td>Revert
 * the display to match that of <code>getValue</code>, possibly losing the current edit.
 * <tr><td>JFormattedTextField.COMMIT <td>Commits the current value. If the value being edited isn't
 * considered a legal value by the <code>AbstractFormatter</code> that is, a
 * <code>ParseException</code> is thrown, then the value will not change, and then edited value will
 * persist. <tr><td>JFormattedTextField.COMMIT_OR_REVERT <td>Similar to <code>COMMIT</code>, but if
 * the value isn't legal, behave like <code>REVERT</code>. <tr><td>JFormattedTextField.PERSIST
 * <td>Do nothing, don't obtain a new <code>AbstractFormatter</code>, and don't update the value.
 * </table> The default is <code>JFormattedTextField.COMMIT_OR_REVERT</code>, refer to {@link
 * #setFocusLostBehavior} for more information on this. <p> <code>JFormattedTextField</code> allows
 * the focus to leave, even if the currently edited value is invalid. To lock the focus down while
 * the <code>JFormattedTextField</code> is an invalid edit state you can attach an
 * <code>InputVerifier</code>. The following code snippet shows a potential implementation of such
 * an <code>InputVerifier</code>:
 * <pre>
 * public class FormattedTextFieldVerifier extends InputVerifier {
 *     public boolean verify(JComponent input) {
 *         if (input instanceof JFormattedTextField) {
 *             JFormattedTextField ftf = (JFormattedTextField)input;
 *             AbstractFormatter formatter = ftf.getFormatter();
 *             if (formatter != null) {
 *                 String text = ftf.getText();
 *                 try {
 *                      formatter.stringToValue(text);
 *                      return true;
 *                  } catch (ParseException pe) {
 *                      return false;
 *                  }
 *              }
 *          }
 *          return true;
 *      }
 *      public boolean shouldYieldFocus(JComponent input) {
 *          return verify(input);
 *      }
 *  }
 * </pre>
 * <p> Alternatively, you could invoke <code>commitEdit</code>, which would also commit the value.
 * <p> <code>JFormattedTextField</code> does not do the formatting it self, rather formatting is
 * done through an instance of <code>JFormattedTextField.AbstractFormatter</code> which is obtained
 * from an instance of <code>JFormattedTextField.AbstractFormatterFactory</code>. Instances of
 * <code>JFormattedTextField.AbstractFormatter</code> are notified when they become active by way of
 * the <code>install</code> method, at which point the <code>JFormattedTextField.AbstractFormatter</code>
 * can install whatever it needs to, typically a <code>DocumentFilter</code>. Similarly when
 * <code>JFormattedTextField</code> no longer needs the <code>AbstractFormatter</code>, it will
 * invoke <code>uninstall</code>. <p> <code>JFormattedTextField</code> typically queries the
 * <code>AbstractFormatterFactory</code> for an <code>AbstractFormat</code> when it gains or loses
 * focus. Although this can change based on the focus lost policy. If the focus lost policy is
 * <code>JFormattedTextField.PERSIST</code> and the <code>JFormattedTextField</code> has been
 * edited, the <code>AbstractFormatterFactory</code> will not be queried until the value has been
 * committed. Similarly if the focus lost policy is <code>JFormattedTextField.COMMIT</code> and an
 * exception is thrown from <code>stringToValue</code>, the <code>AbstractFormatterFactory</code>
 * will not be queried when focus is lost or gained. <p> <code>JFormattedTextField.AbstractFormatter</code>
 * is also responsible for determining when values are committed to the
 * <code>JFormattedTextField</code>. Some <code>JFormattedTextField.AbstractFormatter</code>s will
 * make new values available on every edit, and others will never commit the value. You can force
 * the current value to be obtained from the current <code>JFormattedTextField.AbstractFormatter</code>
 * by way of invoking <code>commitEdit</code>. <code>commitEdit</code> will be invoked whenever
 * return is pressed in the <code>JFormattedTextField</code>. <p> If an
 * <code>AbstractFormatterFactory</code> has not been explicitly set, one will be set based on the
 * <code>Class</code> of the value type after <code>setValue</code> has been invoked (assuming value
 * is non-null). For example, in the following code an appropriate <code>AbstractFormatterFactory</code>
 * and <code>AbstractFormatter</code> will be created to handle formatting of numbers:
 * <pre>
 *   JFormattedTextField tf = new JFormattedTextField();
 *   tf.setValue(100);
 * </pre>
 * <p> <strong>Warning:</strong> As the <code>AbstractFormatter</code> will typically install a
 * <code>DocumentFilter</code> on the <code>Document</code>, and a <code>NavigationFilter</code> on
 * the <code>JFormattedTextField</code> you should not install your own. If you do, you are likely
 * to see odd behavior in that the editing policy of the <code>AbstractFormatter</code> will not be
 * enforced. <p> <strong>Warning:</strong> Swing is not thread safe. For more information see <a
 * href="package-summary.html#threading">Swing's Threading Policy</a>. <p> <strong>Warning:</strong>
 * Serialized objects of this class will not be compatible with future Swing releases. The current
 * serialization support is appropriate for short term storage or RMI between applications running
 * the same version of Swing.  As of 1.4, support for long term storage of all JavaBeans&trade; has
 * been added to the <code>java.beans</code> package. Please see {@link java.beans.XMLEncoder}.
 *
 * @since 1.4
 */
public class JFormattedTextField extends JTextField {

  private static final String uiClassID = "FormattedTextFieldUI";
  private static final Action[] defaultActions =
      {new CommitAction(), new CancelAction()};

  /**
   * Constant identifying that when focus is lost,
   * <code>commitEdit</code> should be invoked. If in committing the
   * new value a <code>ParseException</code> is thrown, the invalid
   * value will remain.
   *
   * @see #setFocusLostBehavior
   */
  public static final int COMMIT = 0;

  /**
   * Constant identifying that when focus is lost,
   * <code>commitEdit</code> should be invoked. If in committing the new
   * value a <code>ParseException</code> is thrown, the value will be
   * reverted.
   *
   * @see #setFocusLostBehavior
   */
  public static final int COMMIT_OR_REVERT = 1;

  /**
   * Constant identifying that when focus is lost, editing value should
   * be reverted to current value set on the
   * <code>JFormattedTextField</code>.
   *
   * @see #setFocusLostBehavior
   */
  public static final int REVERT = 2;

  /**
   * Constant identifying that when focus is lost, the edited value
   * should be left.
   *
   * @see #setFocusLostBehavior
   */
  public static final int PERSIST = 3;


  /**
   * Factory used to obtain an instance of AbstractFormatter.
   */
  private AbstractFormatterFactory factory;
  /**
   * Object responsible for formatting the current value.
   */
  private AbstractFormatter format;
  /**
   * Last valid value.
   */
  private Object value;
  /**
   * True while the value being edited is valid.
   */
  private boolean editValid;
  /**
   * Behavior when focus is lost.
   */
  private int focusLostBehavior;
  /**
   * Indicates the current value has been edited.
   */
  private boolean edited;
  /**
   * Used to set the dirty state.
   */
  private DocumentListener documentListener;
  /**
   * Masked used to set the AbstractFormatterFactory.
   */
  private Object mask;
  /**
   * ActionMap that the TextFormatter Actions are added to.
   */
  private ActionMap textFormatterActionMap;
  /**
   * Indicates the input method composed text is in the document
   */
  private boolean composedTextExists = false;
  /**
   * A handler for FOCUS_LOST event
   */
  private FocusLostHandler focusLostHandler;


  /**
   * Creates a <code>JFormattedTextField</code> with no
   * <code>AbstractFormatterFactory</code>. Use <code>setMask</code> or
   * <code>setFormatterFactory</code> to configure the
   * <code>JFormattedTextField</code> to edit a particular type of
   * value.
   */
  public JFormattedTextField() {
    super();
    enableEvents(AWTEvent.FOCUS_EVENT_MASK);
    setFocusLostBehavior(COMMIT_OR_REVERT);
  }

  /**
   * Creates a JFormattedTextField with the specified value. This will
   * create an <code>AbstractFormatterFactory</code> based on the
   * type of <code>value</code>.
   *
   * @param value Initial value for the JFormattedTextField
   */
  public JFormattedTextField(Object value) {
    this();
    setValue(value);
  }

  /**
   * Creates a <code>JFormattedTextField</code>. <code>format</code> is
   * wrapped in an appropriate <code>AbstractFormatter</code> which is
   * then wrapped in an <code>AbstractFormatterFactory</code>.
   *
   * @param format Format used to look up an AbstractFormatter
   */
  public JFormattedTextField(java.text.Format format) {
    this();
    setFormatterFactory(getDefaultFormatterFactory(format));
  }

  /**
   * Creates a <code>JFormattedTextField</code> with the specified
   * <code>AbstractFormatter</code>. The <code>AbstractFormatter</code>
   * is placed in an <code>AbstractFormatterFactory</code>.
   *
   * @param formatter AbstractFormatter to use for formatting.
   */
  public JFormattedTextField(AbstractFormatter formatter) {
    this(new DefaultFormatterFactory(formatter));
  }

  /**
   * Creates a <code>JFormattedTextField</code> with the specified
   * <code>AbstractFormatterFactory</code>.
   *
   * @param factory AbstractFormatterFactory used for formatting.
   */
  public JFormattedTextField(AbstractFormatterFactory factory) {
    this();
    setFormatterFactory(factory);
  }

  /**
   * Creates a <code>JFormattedTextField</code> with the specified
   * <code>AbstractFormatterFactory</code> and initial value.
   *
   * @param factory <code>AbstractFormatterFactory</code> used for formatting.
   * @param currentValue Initial value to use
   */
  public JFormattedTextField(AbstractFormatterFactory factory,
      Object currentValue) {
    this(currentValue);
    setFormatterFactory(factory);
  }

  /**
   * Sets the behavior when focus is lost. This will be one of
   * <code>JFormattedTextField.COMMIT_OR_REVERT</code>,
   * <code>JFormattedTextField.REVERT</code>,
   * <code>JFormattedTextField.COMMIT</code> or
   * <code>JFormattedTextField.PERSIST</code>
   * Note that some <code>AbstractFormatter</code>s may push changes as
   * they occur, so that the value of this will have no effect.
   * <p>
   * This will throw an <code>IllegalArgumentException</code> if the object
   * passed in is not one of the afore mentioned values.
   * <p>
   * The default value of this property is
   * <code>JFormattedTextField.COMMIT_OR_REVERT</code>.
   *
   * @param behavior Identifies behavior when focus is lost
   * @throws IllegalArgumentException if behavior is not one of the known values
   * @beaninfo enum: COMMIT         JFormattedTextField.COMMIT COMMIT_OR_REVERT
   * JFormattedTextField.COMMIT_OR_REVERT REVERT         JFormattedTextField.REVERT PERSIST
   * JFormattedTextField.PERSIST description: Behavior when component loses focus
   */
  public void setFocusLostBehavior(int behavior) {
    if (behavior != COMMIT && behavior != COMMIT_OR_REVERT &&
        behavior != PERSIST && behavior != REVERT) {
      throw new IllegalArgumentException(
          "setFocusLostBehavior must be one of: JFormattedTextField.COMMIT, JFormattedTextField.COMMIT_OR_REVERT, JFormattedTextField.PERSIST or JFormattedTextField.REVERT");
    }
    focusLostBehavior = behavior;
  }

  /**
   * Returns the behavior when focus is lost. This will be one of
   * <code>COMMIT_OR_REVERT</code>,
   * <code>COMMIT</code>,
   * <code>REVERT</code> or
   * <code>PERSIST</code>
   * Note that some <code>AbstractFormatter</code>s may push changes as
   * they occur, so that the value of this will have no effect.
   *
   * @return returns behavior when focus is lost
   */
  public int getFocusLostBehavior() {
    return focusLostBehavior;
  }

  /**
   * Sets the <code>AbstractFormatterFactory</code>.
   * <code>AbstractFormatterFactory</code> is
   * able to return an instance of <code>AbstractFormatter</code> that is
   * used to format a value for display, as well an enforcing an editing
   * policy.
   * <p>
   * If you have not explicitly set an <code>AbstractFormatterFactory</code>
   * by way of this method (or a constructor) an
   * <code>AbstractFormatterFactory</code> and consequently an
   * <code>AbstractFormatter</code> will be used based on the
   * <code>Class</code> of the value. <code>NumberFormatter</code> will
   * be used for <code>Number</code>s, <code>DateFormatter</code> will
   * be used for <code>Dates</code>, otherwise <code>DefaultFormatter</code>
   * will be used.
   * <p>
   * This is a JavaBeans bound property.
   *
   * @param tf <code>AbstractFormatterFactory</code> used to lookup instances of
   * <code>AbstractFormatter</code>
   * @beaninfo bound: true attribute: visualUpdate true description: AbstractFormatterFactory,
   * responsible for returning an AbstractFormatter that can format the current value.
   */
  public void setFormatterFactory(AbstractFormatterFactory tf) {
    AbstractFormatterFactory oldFactory = factory;

    factory = tf;
    firePropertyChange("formatterFactory", oldFactory, tf);
    setValue(getValue(), true, false);
  }

  /**
   * Returns the current <code>AbstractFormatterFactory</code>.
   *
   * @return <code>AbstractFormatterFactory</code> used to determine <code>AbstractFormatter</code>s
   * @see #setFormatterFactory
   */
  public AbstractFormatterFactory getFormatterFactory() {
    return factory;
  }

  /**
   * Sets the current <code>AbstractFormatter</code>.
   * <p>
   * You should not normally invoke this, instead set the
   * <code>AbstractFormatterFactory</code> or set the value.
   * <code>JFormattedTextField</code> will
   * invoke this as the state of the <code>JFormattedTextField</code>
   * changes and requires the value to be reset.
   * <code>JFormattedTextField</code> passes in the
   * <code>AbstractFormatter</code> obtained from the
   * <code>AbstractFormatterFactory</code>.
   * <p>
   * This is a JavaBeans bound property.
   *
   * @param format AbstractFormatter to use for formatting
   * @beaninfo bound: true attribute: visualUpdate true description: TextFormatter, responsible for
   * formatting the current value
   * @see #setFormatterFactory
   */
  protected void setFormatter(AbstractFormatter format) {
    AbstractFormatter oldFormat = this.format;

    if (oldFormat != null) {
      oldFormat.uninstall();
    }
    setEditValid(true);
    this.format = format;
    if (format != null) {
      format.install(this);
    }
    setEdited(false);
    firePropertyChange("textFormatter", oldFormat, format);
  }

  /**
   * Returns the <code>AbstractFormatter</code> that is used to format and
   * parse the current value.
   *
   * @return AbstractFormatter used for formatting
   */
  public AbstractFormatter getFormatter() {
    return format;
  }

  /**
   * Sets the value that will be formatted by an
   * <code>AbstractFormatter</code> obtained from the current
   * <code>AbstractFormatterFactory</code>. If no
   * <code>AbstractFormatterFactory</code> has been specified, this will
   * attempt to create one based on the type of <code>value</code>.
   * <p>
   * The default value of this property is null.
   * <p>
   * This is a JavaBeans bound property.
   *
   * @param value Current value to display
   * @beaninfo bound: true attribute: visualUpdate true description: The value to be formatted.
   */
  public void setValue(Object value) {
    if (value != null && getFormatterFactory() == null) {
      setFormatterFactory(getDefaultFormatterFactory(value));
    }
    setValue(value, true, true);
  }

  /**
   * Returns the last valid value. Based on the editing policy of
   * the <code>AbstractFormatter</code> this may not return the current
   * value. The currently edited value can be obtained by invoking
   * <code>commitEdit</code> followed by <code>getValue</code>.
   *
   * @return Last valid value
   */
  public Object getValue() {
    return value;
  }

  /**
   * Forces the current value to be taken from the
   * <code>AbstractFormatter</code> and set as the current value.
   * This has no effect if there is no current
   * <code>AbstractFormatter</code> installed.
   *
   * @throws ParseException if the <code>AbstractFormatter</code> is not able to format the current
   * value
   */
  public void commitEdit() throws ParseException {
    AbstractFormatter format = getFormatter();

    if (format != null) {
      setValue(format.stringToValue(getText()), false, true);
    }
  }

  /**
   * Sets the validity of the edit on the receiver. You should not normally
   * invoke this. This will be invoked by the
   * <code>AbstractFormatter</code> as the user edits the value.
   * <p>
   * Not all formatters will allow the component to get into an invalid
   * state, and thus this may never be invoked.
   * <p>
   * Based on the look and feel this may visually change the state of
   * the receiver.
   *
   * @param isValid boolean indicating if the currently edited value is valid.
   * @beaninfo bound: true attribute: visualUpdate true description: True indicates the edited value
   * is valid
   */
  private void setEditValid(boolean isValid) {
    if (isValid != editValid) {
      editValid = isValid;
      firePropertyChange("editValid", Boolean.valueOf(!isValid),
          Boolean.valueOf(isValid));
    }
  }

  /**
   * Returns true if the current value being edited is valid. The value of
   * this is managed by the current <code>AbstractFormatter</code>, as such
   * there is no public setter for it.
   *
   * @return true if the current value being edited is valid.
   */
  public boolean isEditValid() {
    return editValid;
  }

  /**
   * Invoked when the user inputs an invalid value. This gives the
   * component a chance to provide feedback. The default
   * implementation beeps.
   */
  protected void invalidEdit() {
    UIManager.getLookAndFeel().provideErrorFeedback(JFormattedTextField.this);
  }

  /**
   * Processes any input method events, such as
   * <code>InputMethodEvent.INPUT_METHOD_TEXT_CHANGED</code> or
   * <code>InputMethodEvent.CARET_POSITION_CHANGED</code>.
   *
   * @param e the <code>InputMethodEvent</code>
   * @see InputMethodEvent
   */
  protected void processInputMethodEvent(InputMethodEvent e) {
    AttributedCharacterIterator text = e.getText();
    int commitCount = e.getCommittedCharacterCount();

    // Keep track of the composed text
    if (text != null) {
      int begin = text.getBeginIndex();
      int end = text.getEndIndex();
      composedTextExists = ((end - begin) > commitCount);
    } else {
      composedTextExists = false;
    }

    super.processInputMethodEvent(e);
  }

  /**
   * Processes any focus events, such as
   * <code>FocusEvent.FOCUS_GAINED</code> or
   * <code>FocusEvent.FOCUS_LOST</code>.
   *
   * @param e the <code>FocusEvent</code>
   * @see FocusEvent
   */
  protected void processFocusEvent(FocusEvent e) {
    super.processFocusEvent(e);

    // ignore temporary focus event
    if (e.isTemporary()) {
      return;
    }

    if (isEdited() && e.getID() == FocusEvent.FOCUS_LOST) {
      InputContext ic = getInputContext();
      if (focusLostHandler == null) {
        focusLostHandler = new FocusLostHandler();
      }

      // if there is a composed text, process it first
      if ((ic != null) && composedTextExists) {
        ic.endComposition();
        EventQueue.invokeLater(focusLostHandler);
      } else {
        focusLostHandler.run();
      }
    } else if (!isEdited()) {
      // reformat
      setValue(getValue(), true, true);
    }
  }

  /**
   * FOCUS_LOST behavior implementation
   */
  private class FocusLostHandler implements Runnable, Serializable {

    public void run() {
      int fb = JFormattedTextField.this.getFocusLostBehavior();
      if (fb == JFormattedTextField.COMMIT ||
          fb == JFormattedTextField.COMMIT_OR_REVERT) {
        try {
          JFormattedTextField.this.commitEdit();
          // Give it a chance to reformat.
          JFormattedTextField.this.setValue(
              JFormattedTextField.this.getValue(), true, true);
        } catch (ParseException pe) {
          if (fb == JFormattedTextField.this.COMMIT_OR_REVERT) {
            JFormattedTextField.this.setValue(
                JFormattedTextField.this.getValue(), true, true);
          }
        }
      } else if (fb == JFormattedTextField.REVERT) {
        JFormattedTextField.this.setValue(
            JFormattedTextField.this.getValue(), true, true);
      }
    }
  }

  /**
   * Fetches the command list for the editor.  This is
   * the list of commands supported by the plugged-in UI
   * augmented by the collection of commands that the
   * editor itself supports.  These are useful for binding
   * to events, such as in a keymap.
   *
   * @return the command list
   */
  public Action[] getActions() {
    return TextAction.augmentList(super.getActions(), defaultActions);
  }

  /**
   * Gets the class ID for a UI.
   *
   * @return the string "FormattedTextFieldUI"
   * @see JComponent#getUIClassID
   */
  public String getUIClassID() {
    return uiClassID;
  }

  /**
   * Associates the editor with a text document.
   * The currently registered factory is used to build a view for
   * the document, which gets displayed by the editor after revalidation.
   * A PropertyChange event ("document") is propagated to each listener.
   *
   * @param doc the document to display/edit
   * @beaninfo description: the text document model bound: true expert: true
   * @see #getDocument
   */
  public void setDocument(Document doc) {
    if (documentListener != null && getDocument() != null) {
      getDocument().removeDocumentListener(documentListener);
    }
    super.setDocument(doc);
    if (documentListener == null) {
      documentListener = new DocumentHandler();
    }
    doc.addDocumentListener(documentListener);
  }

  /*
   * See readObject and writeObject in JComponent for more
   * information about serialization in Swing.
   *
   * @param s Stream to write to
   */
  private void writeObject(ObjectOutputStream s) throws IOException {
    s.defaultWriteObject();
    if (getUIClassID().equals(uiClassID)) {
      byte count = JComponent.getWriteObjCounter(this);
      JComponent.setWriteObjCounter(this, --count);
      if (count == 0 && ui != null) {
        ui.installUI(this);
      }
    }
  }

  /**
   * Resets the Actions that come from the TextFormatter to
   * <code>actions</code>.
   */
  private void setFormatterActions(Action[] actions) {
    if (actions == null) {
      if (textFormatterActionMap != null) {
        textFormatterActionMap.clear();
      }
    } else {
      if (textFormatterActionMap == null) {
        ActionMap map = getActionMap();

        textFormatterActionMap = new ActionMap();
        while (map != null) {
          ActionMap parent = map.getParent();

          if (parent instanceof UIResource || parent == null) {
            map.setParent(textFormatterActionMap);
            textFormatterActionMap.setParent(parent);
            break;
          }
          map = parent;
        }
      }
      for (int counter = actions.length - 1; counter >= 0;
          counter--) {
        Object key = actions[counter].getValue(Action.NAME);

        if (key != null) {
          textFormatterActionMap.put(key, actions[counter]);
        }
      }
    }
  }

  /**
   * Does the setting of the value. If <code>createFormat</code> is true,
   * this will also obtain a new <code>AbstractFormatter</code> from the
   * current factory. The property change event will be fired if
   * <code>firePC</code> is true.
   */
  private void setValue(Object value, boolean createFormat, boolean firePC) {
    Object oldValue = this.value;

    this.value = value;

    if (createFormat) {
      AbstractFormatterFactory factory = getFormatterFactory();
      AbstractFormatter atf;

      if (factory != null) {
        atf = factory.getFormatter(this);
      } else {
        atf = null;
      }
      setFormatter(atf);
    } else {
      // Assumed to be valid
      setEditValid(true);
    }

    setEdited(false);

    if (firePC) {
      firePropertyChange("value", oldValue, value);
    }
  }

  /**
   * Sets the edited state of the receiver.
   */
  private void setEdited(boolean edited) {
    this.edited = edited;
  }

  /**
   * Returns true if the receiver has been edited.
   */
  private boolean isEdited() {
    return edited;
  }

  /**
   * Returns an AbstractFormatterFactory suitable for the passed in
   * Object type.
   */
  private AbstractFormatterFactory getDefaultFormatterFactory(Object type) {
    if (type instanceof DateFormat) {
      return new DefaultFormatterFactory(new DateFormatter
          ((DateFormat) type));
    }
    if (type instanceof NumberFormat) {
      return new DefaultFormatterFactory(new NumberFormatter(
          (NumberFormat) type));
    }
    if (type instanceof Format) {
      return new DefaultFormatterFactory(new InternationalFormatter(
          (Format) type));
    }
    if (type instanceof Date) {
      return new DefaultFormatterFactory(new DateFormatter());
    }
    if (type instanceof Number) {
      AbstractFormatter displayFormatter = new NumberFormatter();
      ((NumberFormatter) displayFormatter).setValueClass(type.getClass());
      AbstractFormatter editFormatter = new NumberFormatter(
          new DecimalFormat("#.#"));
      ((NumberFormatter) editFormatter).setValueClass(type.getClass());

      return new DefaultFormatterFactory(displayFormatter,
          displayFormatter, editFormatter);
    }
    return new DefaultFormatterFactory(new DefaultFormatter());
  }


  /**
   * Instances of <code>AbstractFormatterFactory</code> are used by
   * <code>JFormattedTextField</code> to obtain instances of
   * <code>AbstractFormatter</code> which in turn are used to format values.
   * <code>AbstractFormatterFactory</code> can return different
   * <code>AbstractFormatter</code>s based on the state of the
   * <code>JFormattedTextField</code>, perhaps returning different
   * <code>AbstractFormatter</code>s when the
   * <code>JFormattedTextField</code> has focus vs when it
   * doesn't have focus.
   *
   * @since 1.4
   */
  public static abstract class AbstractFormatterFactory {

    /**
     * Returns an <code>AbstractFormatter</code> that can handle formatting
     * of the passed in <code>JFormattedTextField</code>.
     *
     * @param tf JFormattedTextField requesting AbstractFormatter
     * @return AbstractFormatter to handle formatting duties, a null return value implies the
     * JFormattedTextField should behave like a normal JTextField
     */
    public abstract AbstractFormatter getFormatter(JFormattedTextField tf);
  }


  /**
   * Instances of <code>AbstractFormatter</code> are used by
   * <code>JFormattedTextField</code> to handle the conversion both
   * from an Object to a String, and back from a String to an Object.
   * <code>AbstractFormatter</code>s can also enforce editing policies,
   * or navigation policies, or manipulate the
   * <code>JFormattedTextField</code> in any way it sees fit to
   * enforce the desired policy.
   * <p>
   * An <code>AbstractFormatter</code> can only be active in
   * one <code>JFormattedTextField</code> at a time.
   * <code>JFormattedTextField</code> invokes
   * <code>install</code> when it is ready to use it followed
   * by <code>uninstall</code> when done. Subclasses
   * that wish to install additional state should override
   * <code>install</code> and message super appropriately.
   * <p>
   * Subclasses must override the conversion methods
   * <code>stringToValue</code> and <code>valueToString</code>. Optionally
   * they can override <code>getActions</code>,
   * <code>getNavigationFilter</code> and <code>getDocumentFilter</code>
   * to restrict the <code>JFormattedTextField</code> in a particular
   * way.
   * <p>
   * Subclasses that allow the <code>JFormattedTextField</code> to be in
   * a temporarily invalid state should invoke <code>setEditValid</code>
   * at the appropriate times.
   *
   * @since 1.4
   */
  public static abstract class AbstractFormatter implements Serializable {

    private JFormattedTextField ftf;

    /**
     * Installs the <code>AbstractFormatter</code> onto a particular
     * <code>JFormattedTextField</code>.
     * This will invoke <code>valueToString</code> to convert the
     * current value from the <code>JFormattedTextField</code> to
     * a String. This will then install the <code>Action</code>s from
     * <code>getActions</code>, the <code>DocumentFilter</code>
     * returned from <code>getDocumentFilter</code> and the
     * <code>NavigationFilter</code> returned from
     * <code>getNavigationFilter</code> onto the
     * <code>JFormattedTextField</code>.
     * <p>
     * Subclasses will typically only need to override this if they
     * wish to install additional listeners on the
     * <code>JFormattedTextField</code>.
     * <p>
     * If there is a <code>ParseException</code> in converting the
     * current value to a String, this will set the text to an empty
     * String, and mark the <code>JFormattedTextField</code> as being
     * in an invalid state.
     * <p>
     * While this is a public method, this is typically only useful
     * for subclassers of <code>JFormattedTextField</code>.
     * <code>JFormattedTextField</code> will invoke this method at
     * the appropriate times when the value changes, or its internal
     * state changes.  You will only need to invoke this yourself if
     * you are subclassing <code>JFormattedTextField</code> and
     * installing/uninstalling <code>AbstractFormatter</code> at a
     * different time than <code>JFormattedTextField</code> does.
     *
     * @param ftf JFormattedTextField to format for, may be null indicating uninstall from current
     * JFormattedTextField.
     */
    public void install(JFormattedTextField ftf) {
      if (this.ftf != null) {
        uninstall();
      }
      this.ftf = ftf;
      if (ftf != null) {
        try {
          ftf.setText(valueToString(ftf.getValue()));
        } catch (ParseException pe) {
          ftf.setText("");
          setEditValid(false);
        }
        installDocumentFilter(getDocumentFilter());
        ftf.setNavigationFilter(getNavigationFilter());
        ftf.setFormatterActions(getActions());
      }
    }

    /**
     * Uninstalls any state the <code>AbstractFormatter</code> may have
     * installed on the <code>JFormattedTextField</code>. This resets the
     * <code>DocumentFilter</code>, <code>NavigationFilter</code>
     * and additional <code>Action</code>s installed on the
     * <code>JFormattedTextField</code>.
     */
    public void uninstall() {
      if (this.ftf != null) {
        installDocumentFilter(null);
        this.ftf.setNavigationFilter(null);
        this.ftf.setFormatterActions(null);
      }
    }

    /**
     * Parses <code>text</code> returning an arbitrary Object. Some
     * formatters may return null.
     *
     * @param text String to convert
     * @return Object representation of text
     * @throws ParseException if there is an error in the conversion
     */
    public abstract Object stringToValue(String text) throws
        ParseException;

    /**
     * Returns the string value to display for <code>value</code>.
     *
     * @param value Value to convert
     * @return String representation of value
     * @throws ParseException if there is an error in the conversion
     */
    public abstract String valueToString(Object value) throws
        ParseException;

    /**
     * Returns the current <code>JFormattedTextField</code> the
     * <code>AbstractFormatter</code> is installed on.
     *
     * @return JFormattedTextField formatting for.
     */
    protected JFormattedTextField getFormattedTextField() {
      return ftf;
    }

    /**
     * This should be invoked when the user types an invalid character.
     * This forwards the call to the current JFormattedTextField.
     */
    protected void invalidEdit() {
      JFormattedTextField ftf = getFormattedTextField();

      if (ftf != null) {
        ftf.invalidEdit();
      }
    }

    /**
     * Invoke this to update the <code>editValid</code> property of the
     * <code>JFormattedTextField</code>. If you an enforce a policy
     * such that the <code>JFormattedTextField</code> is always in a
     * valid state, you will never need to invoke this.
     *
     * @param valid Valid state of the JFormattedTextField
     */
    protected void setEditValid(boolean valid) {
      JFormattedTextField ftf = getFormattedTextField();

      if (ftf != null) {
        ftf.setEditValid(valid);
      }
    }

    /**
     * Subclass and override if you wish to provide a custom set of
     * <code>Action</code>s. <code>install</code> will install these
     * on the <code>JFormattedTextField</code>'s <code>ActionMap</code>.
     *
     * @return Array of Actions to install on JFormattedTextField
     */
    protected Action[] getActions() {
      return null;
    }

    /**
     * Subclass and override if you wish to provide a
     * <code>DocumentFilter</code> to restrict what can be input.
     * <code>install</code> will install the returned value onto
     * the <code>JFormattedTextField</code>.
     *
     * @return DocumentFilter to restrict edits
     */
    protected DocumentFilter getDocumentFilter() {
      return null;
    }

    /**
     * Subclass and override if you wish to provide a filter to restrict
     * where the user can navigate to.
     * <code>install</code> will install the returned value onto
     * the <code>JFormattedTextField</code>.
     *
     * @return NavigationFilter to restrict navigation
     */
    protected NavigationFilter getNavigationFilter() {
      return null;
    }

    /**
     * Clones the <code>AbstractFormatter</code>. The returned instance
     * is not associated with a <code>JFormattedTextField</code>.
     *
     * @return Copy of the AbstractFormatter
     */
    protected Object clone() throws CloneNotSupportedException {
      AbstractFormatter formatter = (AbstractFormatter) super.clone();

      formatter.ftf = null;
      return formatter;
    }

    /**
     * Installs the <code>DocumentFilter</code> <code>filter</code>
     * onto the current <code>JFormattedTextField</code>.
     *
     * @param filter DocumentFilter to install on the Document.
     */
    private void installDocumentFilter(DocumentFilter filter) {
      JFormattedTextField ftf = getFormattedTextField();

      if (ftf != null) {
        Document doc = ftf.getDocument();

        if (doc instanceof AbstractDocument) {
          ((AbstractDocument) doc).setDocumentFilter(filter);
        }
        doc.putProperty(DocumentFilter.class, null);
      }
    }
  }


  /**
   * Used to commit the edit. This extends JTextField.NotifyAction
   * so that <code>isEnabled</code> is true while a JFormattedTextField
   * has focus, and extends <code>actionPerformed</code> to invoke
   * commitEdit.
   */
  static class CommitAction extends JTextField.NotifyAction {

    public void actionPerformed(ActionEvent e) {
      JTextComponent target = getFocusedComponent();

      if (target instanceof JFormattedTextField) {
        // Attempt to commit the value
        try {
          ((JFormattedTextField) target).commitEdit();
        } catch (ParseException pe) {
          ((JFormattedTextField) target).invalidEdit();
          // value not committed, don't notify ActionListeners
          return;
        }
      }
      // Super behavior.
      super.actionPerformed(e);
    }

    public boolean isEnabled() {
      JTextComponent target = getFocusedComponent();
      if (target instanceof JFormattedTextField) {
        JFormattedTextField ftf = (JFormattedTextField) target;
        if (!ftf.isEdited()) {
          return false;
        }
        return true;
      }
      return super.isEnabled();
    }
  }


  /**
   * CancelAction will reset the value in the JFormattedTextField when
   * <code>actionPerformed</code> is invoked. It will only be
   * enabled if the focused component is an instance of
   * JFormattedTextField.
   */
  private static class CancelAction extends TextAction {

    public CancelAction() {
      super("reset-field-edit");
    }

    public void actionPerformed(ActionEvent e) {
      JTextComponent target = getFocusedComponent();

      if (target instanceof JFormattedTextField) {
        JFormattedTextField ftf = (JFormattedTextField) target;
        ftf.setValue(ftf.getValue());
      }
    }

    public boolean isEnabled() {
      JTextComponent target = getFocusedComponent();
      if (target instanceof JFormattedTextField) {
        JFormattedTextField ftf = (JFormattedTextField) target;
        if (!ftf.isEdited()) {
          return false;
        }
        return true;
      }
      return super.isEnabled();
    }
  }


  /**
   * Sets the dirty state as the document changes.
   */
  private class DocumentHandler implements DocumentListener, Serializable {

    public void insertUpdate(DocumentEvent e) {
      setEdited(true);
    }

    public void removeUpdate(DocumentEvent e) {
      setEdited(true);
    }

    public void changedUpdate(DocumentEvent e) {
    }
  }
}
